use openssl::asn1::Asn1Time;
use openssl::ec::{EcGroup, EcKey};
use openssl::error::ErrorStack;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::x509::extension::{
    AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
    SubjectKeyIdentifier,
};
use openssl::x509::{X509Builder, X509NameBuilder};

use std::fs;

use crate::bignum::generate_random_serial;
use crate::pair::{CertPair, Certificate, PrivateKey};

pub fn generate(issuer_ca: &CertPair) -> Result<CertPair, ErrorStack> {
    let ec_group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
    let ec_key = EcKey::generate(&ec_group).unwrap();

    let priv_pkey = PKey::from_ec_key(ec_key.clone()).unwrap();
    let pub_pkey = PKey::public_key_from_der(&priv_pkey.public_key_to_der().unwrap()).unwrap();

    let big = generate_random_serial().unwrap();
    let asn1_num = big.to_asn1_integer().unwrap();
    let not_before = Asn1Time::days_from_now(0).unwrap();
    let not_after = Asn1Time::days_from_now(365 * 1).unwrap();

    let mut x509name_builder = X509NameBuilder::new().unwrap();

    x509name_builder
        .append_entry_by_text("CN", "192.168.1.1")
        .unwrap();
    let x509name = x509name_builder.build();
    let issuer = issuer_ca.cert.0.subject_name();
    let subject = &x509name;

    let mut x509_builder = X509Builder::new().unwrap();

    x509_builder.set_version(2).unwrap();
    x509_builder.set_serial_number(&asn1_num).unwrap();
    x509_builder.set_issuer_name(issuer).unwrap();
    x509_builder.set_subject_name(subject).unwrap();
    x509_builder.set_not_before(&not_before).unwrap();
    x509_builder.set_not_after(&not_after).unwrap();
    x509_builder.set_pubkey(&pub_pkey).unwrap();

    let x509_ctx = x509_builder.x509v3_context(Some(&issuer_ca.cert.as_ref()), None);

    let basic_constraints = BasicConstraints::new().critical().build().unwrap();
    let key_usage = KeyUsage::new()
        .digital_signature()
        .key_encipherment()
        .critical()
        .build()
        .unwrap();
    let extended_key_usage = ExtendedKeyUsage::new()
        .server_auth()
        .client_auth()
        .build()
        .unwrap();

    let subject_key_identifier = SubjectKeyIdentifier::new().build(&x509_ctx).unwrap();
    let authority_key_identifier = AuthorityKeyIdentifier::new()
        .keyid(false)
        .issuer(false)
        .build(&x509_ctx)
        .unwrap();
    let subject_alternative_name = SubjectAlternativeName::new()
        .ip("192.168.1.1")
        .build(&x509_ctx)
        .unwrap();

    x509_builder.append_extension(basic_constraints).unwrap();
    x509_builder.append_extension(key_usage).unwrap();
    x509_builder.append_extension(extended_key_usage).unwrap();
    x509_builder
        .append_extension(subject_key_identifier)
        .unwrap();
    x509_builder
        .append_extension(authority_key_identifier)
        .unwrap();
    x509_builder
        .append_extension(subject_alternative_name)
        .unwrap();

    x509_builder
        .sign(issuer_ca.key.as_ref(), MessageDigest::sha256())
        .unwrap();

    let cert = x509_builder.build();

    let key_pem = ec_key.private_key_to_pem().unwrap();
    let cert_pem = cert.to_pem().unwrap();

    fs::write("/root/client-key.pem", key_pem).unwrap();
    fs::write("/root/client-cert.pem", cert_pem).unwrap();

    CertPair::new(Certificate(cert), PrivateKey(priv_pkey))
}
